/*************************************************************************
 *                                                                       *
 * This file is part of the FreeMAPs distribution.  For full copyright   *
 * information, see COPYRIGHT and COPYING.LESSER.                        *
 *                                                                       *
 * Author:           sqbang                                              *
 * Email:            sqbang@ustc.edu                                     *
 * Date:             2011.03.30                                          *
 *                                                                       *
 *************************************************************************/

//define header
#include "COMMON/tbox/Utilities.h"

//C++ headers
#include <iomanip>
#include <fstream>
#include <sys/stat.h>

//COMMON headers
#include "COMMON/expr/expreval.h"
#include "COMMON/info/InfoManager.h"
#include "COMMON/info/CommonInfo.h"
//#include "COMMON/info/VariableInfo.h"
//#include "COMMON/info/TimeInfo.h"
//#include "COMMON/info/MeshInfo.h"
//#include "COMMON/math/Variable.h"
//#include "COMMON/mesh/StructuredMesh.h"
//#include "COMMON/mesh/UnstructuredMesh.h"
#include "COMMON/tbox/InputDatabase.h"
#include "COMMON/tbox/Point.h"

namespace FreeMAPs
{

bool
Utilities::can_access (
    const char* name)
{
    if (access(name, R_OK) == 0)
    {
        return true;
    }

    return false;
}



bool
Utilities::can_modify (
    const char* name)
{
    if (access (name, W_OK | R_OK) == 0)
    {
        return true;
    }

    return false;
}



void
Utilities::check_file_name (
    std::string& file_name)
{
#if 0
    if (Utilities::is_file (file_name.c_str()))
    {
        switch (InfoManager::get_instance()->get_common_info()->get_overwrite_exist_file_manner())
        {
        case WarningLevelType::SELF_DETERMINATION_WITH_WARNING:
            {
                const std::string origin_file_name (file_name);
                file_name = Utilities::get_new_file_name (file_name);
                FREEMAPS_WARNING ("file of \"" << origin_file_name << " exists already\n"
                                  << "a new file name of \"" << file_name << "will be used\n"
                                  << "you can re-set \"base_name\" or change \"overwrite_exist_file_manner\" to \"IGNORE\"\n"
                                  << "to avoid this warning message" << std::endl);

                break;
            }
        case WarningLevelType::SELF_DETERMINATION_WITHOUT_WARNING:
            {
                file_name = Utilities::get_new_file_name (file_name);
                break;
            }
        case WarningLevelType::IGNORE:
            {
                /*
                 * do nothing
                 */
                break;
            }
        case WarningLevelType::WARN:
            {
                FREEMAPS_WARNING ("file of \"" << file_name << " exists already\n"
                                  << "you can re-set \"base_name\" or change \"overwrite_exist_file_manner\" to \"IGNORE\"\n"
                                  << "to avoid this warning message" << std::endl);
                break;
            }
        case WarningLevelType::ERROR:
            {
                FREEMAPS_ERROR ("file of \"" << file_name << " exists already\n"
                                << "you can re-set \"base_name\" or change \"overwrite_exist_file_manner\" to \"IGNORE\"\n"
                                << "to avoid this" << std::endl);
                break;
            }
        default:
            {
                FREEMAPS_ERROR ("not supported warning level type");
            }
        }
    }
#endif
}



std::string
Utilities::int_to_string(
    int num,
    int min_width)
{
    int tmp_width = (min_width > 0 ? min_width : 1);
    std::ostringstream os;
    if (num < 0)
    {
        os << '-' << std::setw(tmp_width - 1) << std::setfill('0') << -num;
    }
    else
    {
        os << std::setw(tmp_width) << std::setfill('0') << num;
    }
    os << std::flush;

    return os.str();  //returns the string form of the stringstream object
}



int
Utilities::get_num_of_same_chars (
    const std::string& str1,
    const std::string& str2)
{
    int max_same_length = 0;
    const int len = static_cast<int>(str2.size());
    for (int istart = 0; istart < len; ++istart)
    {
        for (int ilength = 1; ilength <= len - istart; ++ilength)
        {
            std::string temp_str;
            temp_str.assign (str2, istart, ilength);
            if (str1.find (temp_str) != std::string::npos)
            {
                //std::cout<< "str1 = " <<str1<< ", temp_str = " <<temp_str<< ", size = " <<temp_str.size()
                //<< ", find = " <<str1.find (temp_str) << std::endl;
                max_same_length = std::max(max_same_length, (int)temp_str.size());
            }
        }
    }

    return max_same_length;
}



bool
Utilities::open_file_for_write (
    const std::string& file_name,
    std::ofstream& os)
{
    os.open (file_name.c_str(), std::ios_base::out);
    if (!os)
    {
        std::cout << "cannot open file \"" << file_name << "\"" << std::endl;
        return false;
    }

    return true;
}



bool
Utilities::open_file_for_read (
    const std::string& file_name,
    std::ifstream& os)
{
    os.open (file_name.c_str(), std::ios_base::in);
    if (!os)
    {
        std::cout << "cannot open file \"" << file_name << "\"" << std::endl;
        return false;
    }
    return true;
}



bool
Utilities::close_file (
    std::ofstream& os)
{
    os.close();
    os.clear();
    return true;
}



bool
Utilities::close_file (
    std::ifstream& os)
{
    os.close();
    os.clear();
    return true;
}



std::string
Utilities::get_current_directory_name ()
{
    const int MAX_LEN = 1024;
    char buffer[MAX_LEN];
    getcwd(buffer, MAX_LEN);
    std::string dir (buffer);
    return dir;
}



std::string
Utilities::get_effective_file_name (
    const std::string& path,
    const std::string& default_dir)
{
    //std::cout<< "path = " <<path<<std::endl;
    std::string new_path (path);
    static int recursive_depth = 0;
    if (++recursive_depth > 1e6)
    {
        FREEMAPS_ERROR ("check here for recursive process");
    }
    //std::cout << "recursive_depth = " << ++recursive_depth << std::endl;

    /*
     * first replace vironment variables
     * that means change $VAR by using getenv(VAR).
     */
    for (std::size_t i = 0; i < new_path.size(); ++i)
    {
        if (new_path[i] == '$')
        {
            std::string env_var;
            for (std::size_t j = i + 1; j < new_path.size(); ++j)
            {
                if (new_path[j] != '/')
                {
                    env_var += new_path[j];
                }
                else
                {
                    break;
                }
            }

            if (getenv(env_var.c_str()) != NULL)
            {
                const std::string& env_var_value = getenv(env_var.c_str());
                new_path.replace (i, env_var.size() + 1, env_var_value);
            }
        }
    }

    //std::cout << "path = " << path << std::endl;
    //std::cout << "new_path = " << new_path << std::endl;

    /*
     * secondly replace "//" by "/"
     */
    bool find_2_l = true;
    do
    {
        std::string::size_type pos_find = new_path.find ("//");
        if (pos_find != std::string::npos)
        {
            new_path.replace (pos_find, 2, "/");
        }
        else
        {
            find_2_l = false;
        }
    }
    while (find_2_l);

    //at last remove the last "/"
    std::string::size_type pos_find = new_path.rfind ("/");
    //std::cout<< "pos_find = " <<pos_find<<std::endl;
    if (pos_find == new_path.size() - 1)
    {
        new_path.erase (pos_find, 1);
    }

    /*
     * at last, if a path is a relative directory,
     * then get absolutely directory by adding working_directory
     * in win32, do nothing.
     */
#ifndef FREEMAPS_WIN32
    if (new_path[0] != '/')
    {
        std::string default_abs_dir = default_dir;
        if (default_abs_dir[0] == '.')
        {
            default_abs_dir = get_current_directory_name() + "/" + default_abs_dir;
        }
        FREEMAPS_ASSERT (default_abs_dir[0] == '/');
        new_path = default_abs_dir + "/" + new_path;
        new_path = get_effective_file_name (new_path, default_abs_dir);
        //std::cout << "new path = " << new_path << std::endl;
    }
#endif
    //std::cout<< "new path = " <<new_path<<std::endl;

    return get_string_without_blank(new_path, -2);
}



void
Utilities::parser_file_name (
    const std::string& origin_file_name,
    std::string& dir_name,
    std::string& file_name)
{
    dir_name.clear();
    file_name.clear();

    std::string cur_dir(get_current_directory_name());
    std::string absolute_path = get_effective_file_name (origin_file_name, cur_dir);
    std::string::size_type pos = absolute_path.rfind ('/');
    FREEMAPS_ASSERT (pos != std::string::npos);
    dir_name.assign (absolute_path, 0, pos);
    file_name.assign (absolute_path, pos + 1, absolute_path.size() - pos - 1);
    file_name = Utilities::get_string_without_blank(file_name, -2);
}



bool
Utilities::regular_match(
    const std::string& str,
    const std::string& pattern,
    const bool from_begin, std::string& rest_str)
{
    std::string str_no_blank = Utilities::get_string_without_blank(str, -2);
    if (from_begin)
    {
        if (str_no_blank.find (pattern) == 0)
        {
            rest_str.assign (str_no_blank, pattern.size(), str_no_blank.size());
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        if ((int)str_no_blank.find (pattern) == (int)(str_no_blank.size() - pattern.size()))
        {
            rest_str.assign (str_no_blank, 0, str_no_blank.size() - pattern.size());
            return true;
        }
        else
        {
            return false;
        }
    }
}



std::string
Utilities::get_string_without_blank(
    const std::string& str, int mode)
{
    std::string new_str;
    if (mode == -2)
    {
        //std::cout<< "str here = '" <<str<< "'" << std::endl;
        new_str = delete_blank_around_char(get_string_without_blank(str, 0));
        //std::cout<< "new str here = '" <<new_str<< "'" << std::endl;
    }
    else if (mode == -1)
    {
        for (std::size_t i = 0; i < str.size(); ++i)
        {
            if (!Utilities::is_blank_char(str[i]))
            {
                new_str.append (&str[i], 1);
            }
        }
    }
    else if (mode == 0)
    {
        new_str = Utilities::get_string_without_blank(Utilities::get_string_without_blank(str, 1), 2);
    }
    else if (mode == 1)
    {
        for (std::size_t i = 0; i < str.size(); ++i)
        {
            if (Utilities::is_blank_char(str[i]))
            {
                continue;
            }
            else
            {
                new_str.assign (str, i, str.size());
                break;
            }
        }
    }
    else if (mode == 2)
    {
        for (int i = static_cast<int>(str.size() - 1); i >= 0; --i)
        {
            if (Utilities::is_blank_char(str[i]))
            {
                continue;
            }
            else
            {
                new_str.assign (str, 0, i + 1);
                break;
            }
        }
    }
    else
    {
        FREEMAPS_ERROR ("not definied");
    }
    return new_str;
}



std::string
Utilities::delete_blank_around_char(
    const std::string& str,
    const char c)
{
    if (str.empty())
    {
        return str;
    }

    std::string new_str;
    bool has_blank = false;
    for (int i = 0; i < (int)str.size() - 1; ++i)
    {
        if (is_blank_char(str[i]) && str[i + 1] == c)
        {
            has_blank = true;
            new_str.append (&str[i + 1], 1);
            ++i;
        }
        else if (is_blank_char(str[i + 1]) && str[i] == c)
        {
            has_blank = true;
            new_str.append (&str[i], 1);
            ++i;
        }
        else
        {
            new_str.append (&str[i], 1);
        }
    }

    const int size = (int)str.size();

    if (!is_blank_char(str[size - 1]) || size == 1)
    {
        new_str.append (&str[size - 1], 1);
    }
    else if (str[size - 2] != c)
    {
        new_str.append (&str[size - 1], 1);
    }

    //std::cout<< "str     = '" <<str<< "'\n"
    //         << "new str = '" <<new_str<< "'" << std::endl;

    if (has_blank)
    {
        new_str = delete_blank_around_char(new_str, c);
    }

    return new_str;
}



bool
Utilities::is_blank_char(
    const char c)
{
    if (c == ' ' || c == '\t' || c == '\n')
    {
        return true;
    }
    else
    {
        return false;
    }
}



bool
Utilities::get_similar_string(
    const std::string& wrong_string,
    const std::set<std::string>& allowable_table,
    std::string& similar_string,
    const int same_length)
{
    int max_same_length = 0;
    for (std::set<std::string>::const_iterator pa = allowable_table.begin();
            pa != allowable_table.end(); ++pa)
    {
        int temp_same_length = get_num_of_same_chars (wrong_string, *pa);
        if (temp_same_length > max_same_length)
        {
            max_same_length = temp_same_length;
            similar_string = *pa;
        }

        //std::cout<< "max_same_length = " <<max_same_length<< ", similar_string = " <<similar_string<<std::endl;
    }
    //std::cout<< "at last max_same_length = " <<max_same_length<< ", similar_string = " <<similar_string<<std::endl;

    if (max_same_length >= same_length)
    {
        return true;
    }
    else
    {
        return false;
    }
}


#if 0
Point
Utilities::get_integrated_lower_point (
    const Point& p1,
    const Point& p2)
{
    Point p;

    for (int i = 0; i < FREEMAPS_DIM; ++i)
    {
        p(i) = std::min (p1(i), p2(i));
    }

    return p;
}
#endif

#if 0
Point
Utilities::get_integrated_upper_point (
    const Point& p1,
    const Point& p2)
{
    Point p;

    for (int i = 0; i < FREEMAPS_DIM; ++i)
    {
        p(i) = std::max (p1(i), p2(i));
    }

    return p;
}

#endif

bool
Utilities::is_expreval_lib_reserved_function_name (
    const std::string& name)
{
    std::set<std::string> func_set;

    func_set.insert ("abs");
    func_set.insert ("mod");

    func_set.insert ("ipart");
    func_set.insert ("fpart");

    func_set.insert ("min");
    func_set.insert ("max");
    func_set.insert ("sqrt");

    func_set.insert ("sin");
    func_set.insert ("cos");
    func_set.insert ("tan");

    func_set.insert ("sinh");
    func_set.insert ("cosh");
    func_set.insert ("tanh");

    func_set.insert ("asin");
    func_set.insert ("acos");
    func_set.insert ("atan");
    func_set.insert ("atan2");

    func_set.insert ("log");
    func_set.insert ("ln");
    func_set.insert ("exp");
    func_set.insert ("logn");

    func_set.insert ("ceil");
    func_set.insert ("floor");

    func_set.insert ("rand");
    func_set.insert ("random");
    func_set.insert ("randomize");

    func_set.insert ("deg");
    func_set.insert ("rad");

    func_set.insert ("rect2pol");
    func_set.insert ("pol2rect");

    func_set.insert ("if");
    func_set.insert ("select");

    func_set.insert ("equal");
    func_set.insert ("above");
    func_set.insert ("below");

    func_set.insert ("clip");
    func_set.insert ("clamp");
    func_set.insert ("rescale");

    func_set.insert ("poly");

    func_set.insert ("and");
    func_set.insert ("or");
    func_set.insert ("not");

    func_set.insert ("erf");
    func_set.insert ("erfc");

    if (func_set.find (name) != func_set.end())
    {
        return true;
    }
    else
    {
        return false;
    }
}



bool
Utilities::get_symbols_from_expression (
    const std::string& expr,
    std::set<std::string>& symbols)
{
    ExprEval::ValueList vlist;
    ExprEval::FunctionList flist;
    ExprEval::Expression e;

    flist.AddDefaultFunctions();
    e.SetValueList (&vlist);
    e.SetFunctionList (&flist);

    const bool good_parse = e.Parse (expr, InfoManager::get_instance()->get_common_info()->stop_when_numeric_limits_reached());
    if (good_parse)
    {
        ExprEval::ValueList* value_list = e.GetValueList();

        for (std::size_t i = 0; i < value_list->Count(); ++i)
        {
            const std::string& symbol = value_list->GetItem(i)->GetName();
            symbols.insert (symbol);
        }
        return true;
    }
    else
    {
        return false;
    }
}

} //namespace FreeMAPs
